package com.qf.service.impl;

import com.qf.cache.DefaultRoleCache;
import com.qf.cache.RoleCache;
import com.qf.events.UserLoginEvent;
import com.qf.exceptions.AddDataException;
import com.qf.exceptions.MyBaseException;
import com.qf.exceptions.QueryDataException;
import com.qf.exceptions.ResultCode;

import com.qf.mapper.UserMapper;
import com.qf.pojo.CheckType;
import com.qf.pojo.DefaultRole;
import com.qf.pojo.Role;
import com.qf.pojo.User;
import com.qf.service.UserService;
import com.qf.utils.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;


import java.util.Date;
import java.util.List;

@Service
@Transactional//事务控制
public class UserServiceImpl implements UserService {

    private UserMapper userMapper;

    private DefaultRoleCache defaultRoleCache;

    private RoleCache roleCache;

    private ApplicationContext context;

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    @Autowired
    public void setDefaultRoleCache(DefaultRoleCache defaultRoleCache) {
        this.defaultRoleCache = defaultRoleCache;
    }

    @Autowired
    public void setRoleCache(RoleCache roleCache) {
        this.roleCache = roleCache;
    }

    @Autowired
    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }



    /**
     * 添加用户
     * @param user
     */


    @Override
    public void addUser(User user) {

//        1 用户名允许空吗?
//        if (StringUtils.hasText(user.getUsername())) {//数据不能是null和"" 包括" ",要求至少存在一个有效的字符
//            //返回,用户没有传递必须传递的数据是不是有问题,有问题我们就抛异常,谁愿意处理谁处理,跟我业务没关系,因为我们不负责返回响应结果
//            // 我们要是抛出异常,抛什么?随便一个异常吗? 我们发现我们需要携带数据出去,所以必须使用自定义异常
//            throw new MyBaseException("传递的用户名是空的", ResultCode.USERNAME_NULL);
//        }
//
//        if (user.isEmpty(CheckType.ADD)) {
//            //一定代表着有数据没有传递
//            throw new MyBaseException("传递的用户名是空的", ResultCode.USERNAME_NULL);
//        }
//2 密码允许空吗?
       //断言。我断言这个User一定符合我们的要求，只要不符合我就扔异常
       Assert.isTrue(!user.isEmpty(CheckType.ADD),() ->{
           throw new AddDataException("必须传递的数据不完整,请检查", ResultCode.USERNAME_NULL);
       });

       //第二步 判断数据是不是已经存在

        //用户名允许重复吗？
        //手机号允许重复吗？
        //邮箱允许重复吗？
        //等等
        //根据用户名或者手机号或者邮箱查询用户
        List currentUser = userMapper.selectByUserNameOrPhoneOrEmail(user.getUsername(), user.getPhone(), user.getEmail());        //上面的对象必须是空的，不为空就说明数据已经存在
//        if(currentUser != null){
//            //抛出异常
//        }
        Assert.isTrue(currentUser.size()==0, () -> {
            throw new AddDataException("帐号已经存在", ResultCode.DATA_ALREADY_EXIST);
        });
        // 我们已经能得知的异常能提前处理的就必须要提前处理
        //前端所有的数据都不可信,前端所有的校验都属于无效校验,前端的校验属于可更改操作,服务端必须做校
        //对数据进行操作, 比如密码需要进行"加密",MD SHA 都不是加密, 基于消息摘要算法实现的校验手段(散列值)
        //加密必须能反向解密.   MD sha 有非常大的熵增,数据的丢失   100G的文件--->MD5 32位字符串
        //非加密算法
        //处理密码,将密码改成MD5值
        String password = user.getPassword();//传递的密码
        //String passwordMD5 = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));//将指定的数据转成MD5值
        //如果简单粗暴用上面的方式直接计算md5,相同的密码总是会得到相同的值,那么万一数据泄露了,还是很容易被反向得到密码
        //我们需要喂自己袋盐
        //123456sdfsdfsdfsdafasd-->md5
        //123456hfgdjghkjfghjkfghk-->md5
        //生成随机字符串作为盐
        //程序中有真正的随机数吗?程序中的随机数是利用种子计算出来的
        //如果我想得到一个尽量随机的内容怎么办?避免出现重复内容
        //sessionid 必须不重复,熵池,系统噪声-->计算sessionid
        String salt = MD5Utils.getRandomString(15);//生成一个长度为15的盐
        //至于是拼在什么位置,无所谓,只有一点要求,这边怎么拼的,后面再计算的时候还要怎么拼

        String md5 = MD5Utils.getMd5(password, salt, 1024);
        user.setPassword(md5);
        user.setPwdSalt(salt);
        user.setCreateTime(new Date());
        user.setCreateBy(user.getUsername());
        //数据库是不支持高并发的，你搞了一堆错误进去，不占用数据库的io吗？
        //缓存
        //查询了默认的角色id
        //Todo  问题，我们这个用户的默认的角色id  几乎不会变化，也就是每次注册或者添加什么数据得到的结果总是一样的，有必要每次都查询数据库吗？
//        Long roleId = defaultRoleMapper.selectRoleIdByType("user");
//        //根据角色id查询到了用户角色
//        //TOdo 同理，这个角色变化的频率相对来说也很低
//        Role role = roleMapper.selectRoleById(roleId);
        DefaultRole defaultRole = defaultRoleCache.getValue("user");
        Role role = roleCache.getValue(defaultRole.getRoleId());
//        //设置用户的标识
        user.setRemark(role.getRemark());
        userMapper.addUser(user);
        //用户的角色信息没有保存

    }

    @Override
    public User login(String username, String password) {
        //我们判断的标准适合用户的数据进行比较，如果一致就通过
        //用户数据在什么地方？数据库
        //查询数据库，用什么条件查询？账号查询，账号有唯一索引，密码没有
        //我们要求必须传递账号和密码
        Assert.isTrue(StringUtils.hasText(username)&&StringUtils.hasText(password),()->{
            throw new QueryDataException("账号或者密码错误",ResultCode.USERNAME_PASSWORD_ERROR);
        });

        //如果查询不到,是返回null还是返回一个对象但是里面的属性都是null
        User user = userMapper.selectUserByUserName(username);
        //只要查询不到就一定是账号错误
        Assert.notNull(user,()->{
            throw new QueryDataException("账号或者密码错误",ResultCode.USERNAME_PASSWORD_ERROR);

        });
        //有结果就要比较密码
        //但是用户传递的密码是明文.数据库中是md5值,直接比较不能说一定,但是可以说百分百失败
        //因为MD5不可逆,所以我们只能选择把用户传递的密码再次转成MD5值,然后进行比较,但是转换的时候需要盐
        //盐在用户数据中,我们需要获取,然后一开始注册时候生成MD5的操作重新操作一遍
        String pwdSalt = user.getPwdSalt();//盐
        String comMD5 = MD5Utils.getMd5(password, pwdSalt, 1024);//计算出用户现在传递的密码的MD5
        Assert.isTrue(comMD5.equalsIgnoreCase(user.getPassword()),()->{
            throw new QueryDataException("账号或者密码错误",ResultCode.USERNAME_PASSWORD_ERROR);

        });
        //说明用户传递的账号和密码是对的
        user.setPassword(null);
        user.setPwdSalt(null);
        //发送事件,更新用户的一些登录后的数据
        //context.publishEvent(new UserLoginEvent());
        return user;
    }


    @Override
    public int updateUserLogin(User user){
        //判断时间,不判断,因为这个数据不是用户传递的,是我们自己生成的,我们只要确保一定生成就行
        userMapper.upDateUserLogin(user);
        return 0;
    }
}
